/**
 * Copyright 2007 Luke Pillow
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.orsomethinglikethat.hinge.apt;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.log4j.Logger;

import com.sun.mirror.declaration.ClassDeclaration;
import com.sun.mirror.declaration.ConstructorDeclaration;
import com.sun.mirror.declaration.FieldDeclaration;
import com.sun.mirror.declaration.MemberDeclaration;
import com.sun.mirror.declaration.MethodDeclaration;
import com.sun.mirror.declaration.Modifier;
import com.sun.mirror.type.VoidType;

/**
 * @author lpillow
 */
public class HingeCompilerUtil
{
    /**
     * Private constructor. This is a static utility class and should not be
     * instantiated.`
     */
    private HingeCompilerUtil()
    {
        // Do Nothing
    }

    /**
     * Determine if a {@link FieldDeclaration} has valid modifiers
     * 
     * @param field
     * @return True if all modifiers are valid
     */
    protected static Boolean hasValidModifiers( FieldDeclaration field )
    {
        return !field.getModifiers().contains( Modifier.STATIC ) && !field.getModifiers().contains( Modifier.TRANSIENT );
    }

    /**
     * Determine if a {@link FieldDeclaration} is publicly accessible
     * 
     * @param field
     * @param clazz
     * @return {@link Boolean}
     */
    protected static Boolean hasValidAccessibility( FieldDeclaration field, ClassDeclaration clazz )
    {
        return field.getModifiers().contains( Modifier.PUBLIC ) || HingeCompilerUtil.hasPublicGetter( field, clazz );
    }

    /**
     * Given a {@link ClassDeclaration}, return a collection of
     * {@link FieldDeclaration}s that are considered JavaBean properties.
     * 
     * @param clazz
     * @return Collection of {@link FieldDeclaration} that represent JavaBean
     *         properties
     */
    public static Collection<FieldDeclaration> getValidFields( ClassDeclaration clazz )
    {
        List<FieldDeclaration> validFields = new ArrayList<FieldDeclaration>();

        for ( FieldDeclaration field : clazz.getFields() )
        {
            if ( HingeCompilerUtil.isValidField( field, clazz ) )
            {
                validFields.add( field );
            }
        }

        return validFields;
    }

    /**
     * Does the given {@link ClassDeclaration} have a default public constructor
     * as required for a JavaBean
     * 
     * @param clazz
     * @return true if there is a default public constructor
     */
    protected static Boolean hasDefaultPublicConstructor( ClassDeclaration clazz )
    {
        for ( ConstructorDeclaration constructor : clazz.getConstructors() )
        {
            if ( HingeCompilerUtil.isDefaultPublicConstructor( constructor ) )
            {
                return Boolean.TRUE;
            }
        }

        return Boolean.FALSE;
    }

    /**
     * @param constructor
     * @return true if the given {@link ConstructorDeclaration} is public with
     *         no parameters
     */
    protected static Boolean isDefaultPublicConstructor( ConstructorDeclaration constructor )
    {
        return constructor.getModifiers().contains( Modifier.PUBLIC ) && constructor.getParameters().size() == 0;
    }

    /**
     * @param field
     * @param clazz
     * @return true if there exists a Bean getter and setter for the given field
     *         and clazz.
     */
    protected static Boolean isValidField( FieldDeclaration field, ClassDeclaration clazz )
    {
        return HingeCompilerUtil.hasValidModifiers( field ) && HingeCompilerUtil.hasValidAccessibility( field, clazz );
    }

    /**
     * Determine if there exists a public getter for the given
     * {@link FieldDeclaration}
     * 
     * @param field
     * @param clazz
     * @return true if there is a public getter
     */
    protected static Boolean hasPublicGetter( FieldDeclaration field, ClassDeclaration clazz )
    {
        Boolean result = Boolean.FALSE;

        for ( MethodDeclaration method : clazz.getMethods() )
        {
            if ( HingeCompilerUtil.isPublicGetter( method, field ) )
            {
                result = Boolean.TRUE;
                break;
            }
        }

        return result;
    }

    /**
     * Determine if the given member is public and non-static
     * 
     * @param member
     * @return {@link Boolean}
     */
    protected static Boolean isPublicInstanceMember( MemberDeclaration member )
    {
        return member.getModifiers().contains( Modifier.PUBLIC ) && !member.getModifiers().contains( Modifier.STATIC );
    }

    /**
     * The getter must be public, with no parameters, and the return type must
     * match the field type.
     * 
     * @param method
     * @param field
     * @return true if the is a bean getter for the given field.
     */
    protected static Boolean isPublicGetter( MethodDeclaration method, FieldDeclaration field )
    {
        Logger.getLogger( HingeCompilerUtil.class ).debug( method.getSimpleName() + " isPublicInstanceMember( " + field.getSimpleName() + " ) = " + HingeCompilerUtil.isPublicInstanceMember( method ) );
        Boolean result = HingeCompilerUtil.isPublicInstanceMember( method );

        if ( result && AccessorPrefix.getterFromField( field ).matchesPrefix( method, field ) )
        {
            // No parameters and same return type
            result = method.getParameters().isEmpty() && method.getReturnType().equals( field.getType() );
        }

        return result;
    }

    /**
     * The setter must be public void and accept one parameter of the same type
     * as the given field
     * 
     * @param method
     * @param field
     * @return true if the method is a bean setter for the given field
     */
    protected static Boolean isPublicSetter( MethodDeclaration method, FieldDeclaration field )
    {
        Boolean result = HingeCompilerUtil.isPublicInstanceMember( method );

        if ( result && AccessorPrefix.SET.matchesPrefix( method, field ) )
        {
            // Void return type with one parameter
            if ( method.getReturnType() instanceof VoidType && method.getParameters().size() == 1 )
            {
                // Parameter is of same type as field
                result = method.getParameters().iterator().next().getType().equals( field.getType() );
            }
        }

        return result;
    }

}
